/*-----------------------------------------------------------------------*/
/* MMC/SDSC/SDHC (in SPI mode) control module for STM32 Version 1.1.6    */
/* (C) Martin Thomas, 2010 - based on the AVR MMC module (C)ChaN, 2007   */
/*-----------------------------------------------------------------------*/
/************************************************************/
/*Giao tiep phan cung voi sd card bang mode SPI */
/*************************************************************/
/*-------------------------------------------------------------*/
#include <stm32f10x.h>
#include <diskio.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_spi.h>
#include <stm32f10x_dma.h>

#define STM32_SD_USE_DMA
#ifdef STM32_SD_USE_DMA
// #warning "Information only: using DMA"
#pragma message "*** Using DMA ***"
#endif

/*----------------------------------------------------*/
/* cau hinh phan cung cho board */
/*-----------------------------------------------------*/

#define SPI_SD                   	SPI1
#define GPIO_CS 				  	GPIOB
#define RCC_APB2Periph_GPIO_CS   	RCC_APB2Periph_GPIOB
#define GPIO_Pin_CS              	GPIO_Pin_2
#define RCC_APB2Periph_GPIO_SD   	RCC_APB2Periph_GPIOA
#define GPIO_SPI_SD              	GPIOA
#define GPIO_Pin_SPI_SD_SCK      	GPIO_Pin_5
#define GPIO_Pin_SPI_SD_MISO     	GPIO_Pin_6
#define GPIO_Pin_SPI_SD_MOSI     	GPIO_Pin_7
#define GPIO_Pin_SPI_SD_CD    	    GPIO_Pin_8
#define RCC_APBPeriph_SPI_SD     	RCC_APB2Periph_SPI1
/* - for SPI1 and full-speed APB2: 72MHz/2 */
#define SPI_BaudRatePrescaler_SPI_SD  SPI_BaudRatePrescaler_4

#ifdef STM32_SD_USE_DMA
#define DMA_Channel_SPI_SD_RX    DMA1_Channel2
#define DMA_Channel_SPI_SD_TX    DMA1_Channel3
#define DMA_FLAG_SPI_SD_TC_RX    DMA1_FLAG_TC2
#define DMA_FLAG_SPI_SD_TC_TX    DMA1_FLAG_TC3
#endif

/* Definitions for MMC/SDC command */
#define CMD0	(0x40+0)	/* GO_IDLE_STATE */
#define CMD1	(0x40+1)	/* SEND_OP_COND (MMC) */
#define ACMD41	(0xC0+41)	/* SEND_OP_COND (SDC) */
#define CMD8 	(0x40+8)	/* SEND_IF_COND */
#define CMD9 	(0x40+9)	/* SEND_CSD */
#define CMD10	(0x40+10)	/* SEND_CID */
#define CMD12	(0x40+12)	/* STOP_TRANSMISSION */
#define ACMD13	(0xC0+13)	/* SD_STATUS (SDC) */
#define CMD16	(0x40+16)	/* SET_BLOCKLEN */
#define CMD17	(0x40+17)	/* READ_SINGLE_BLOCK */
#define CMD18	(0x40+18)	/* READ_MULTIPLE_BLOCK */
#define CMD23	(0x40+23)	/* SET_BLOCK_COUNT (MMC) */
#define ACMD23	(0xC0+23)	/* SET_WR_BLK_ERASE_COUNT (SDC) */
#define CMD24	(0x40+24)	/* WRITE_BLOCK */
#define CMD25	(0x40+25)	/* WRITE_MULTIPLE_BLOCK */
#define CMD55	(0x40+55)	/* APP_CMD */
#define CMD58	(0x40+58)	/* READ_OCR */

/* cau hinh chan CS cho sd card */
#define SELECT()        GPIO_ResetBits(GPIO_CS, GPIO_Pin_CS)    /* MMC CS = L */
#define DESELECT()      GPIO_SetBits(GPIO_CS, GPIO_Pin_CS)      /* MMC CS = H */

static volatile DSTATUS Stat = STA_NOINIT;
/*--------------------------------------------------------------------------

 Module Private Functions and Variables

 ---------------------------------------------------------------------------*/

enum speed_setting {
	INTERFACE_SLOW, INTERFACE_FAST
};

static void interface_speed(enum speed_setting speed) {
	uint32_t tmp;

	tmp = SPI_SD->CR1;
	if (speed == INTERFACE_SLOW) {
		/* Set slow clock (100k-400k) */
		tmp = (tmp | SPI_BaudRatePrescaler_256);
	} else {
		/* Set fast clock (depends on the CSD) */
		tmp = (tmp & ~SPI_BaudRatePrescaler_256) | SPI_BaudRatePrescaler_SPI_SD;
	}
	SPI_SD->CR1 = tmp;
}

/*
 *
 *
 */
static BOOL CardDetected(void) {
	if (GPIO_ReadInputDataBit(GPIO_SPI_SD, GPIO_Pin_SPI_SD_CD) == Bit_SET)
		return TRUE;
	else
		return FALSE;
}

/*-----------------------------------------------------------------------*/
/* Transmit/Receive a byte to MMC via SPI  (Platform dependent)          */
/*-----------------------------------------------------------------------*/
static uint8_t stm32_spi_rw(uint8_t out) {
	/* Loop while DR register in not empty */
	// while (SPI_I2S_GetFlagStatus(SPI_SD, SPI_I2S_FLAG_TXE) == RESET) { ; }

	/* Send byte through the SPI peripheral */
	SPI_I2S_SendData(SPI_SD, out);
	/* Loop while DR register is empty */
	/* Wait to receive a byte */
	while (SPI_I2S_GetFlagStatus(SPI_SD, SPI_I2S_FLAG_RXNE) == RESET) {
		;
	}

	/* Return the byte read from the SPI bus */
	return SPI_I2S_ReceiveData(SPI_SD);
}

/*-----------------------------------------------------------------------*/
/* Transmit a byte to MMC via SPI  (Platform dependent)                  */
/*-----------------------------------------------------------------------*/

#define xmit_spi(dat)  stm32_spi_rw(dat)

/*-----------------------------------------------------------------------*/
/* Receive a byte from MMC via SPI  (Platform dependent)                 */
/*-----------------------------------------------------------------------*/
/* Receive a response when sent oxff*/
static uint8_t rcvr_spi(void) {
	return stm32_spi_rw(0xff);
}

/* Alternative macro to receive data fast */
/* dest is address of the memory where held response*/
#define rcvr_spi_m(dst)  *(dst)=stm32_spi_rw(0xff)

/*-----------------------------------------------------------------------*/
/* Wait for card ready                                                   */
/*-----------------------------------------------------------------------*/
/* tra ve oxff neu card san sang, dua vao return de xac dinh trang thai cua card */

static uint8_t wait_ready(void) {
	uint8_t res;
	static uint32_t i;
	i = 82000;

	rcvr_spi(); /* Tranmit oxff and receive response by stm32_spi_rw (oxff) */
	do {
		res = rcvr_spi();
		i--;
	} while ((res != 0xFF) && i > 0);
	return res;
}

/*-----------------------------------------------------------------------*/
/* Deselect the card and release SPI bus                                 */
/*-----------------------------------------------------------------------*/

static
void release_spi(void) {
	DESELECT(); /* CS= HIGHT*/
	rcvr_spi(); /* Need 8 cycle clocks to  succesively finish*/
}

#ifdef STM32_SD_USE_DMA
/*-----------------------------------------------------------------------*/
/* Transmit/Receive Block using DMA (Platform dependent. STM32 here)     */
/*-----------------------------------------------------------------------*/
static
void stm32_dma_transfer(BOOL receive, /* FALSE for buff->SPI, TRUE for SPI->buff               */
const BYTE *buff, /* receive TRUE  : 512 byte data block to be transmitted
 receive FALSE : Data buffer to store received data    */
UINT btr /* receive TRUE  : Byte count (must be multiple of 2)
 receive FALSE : Byte count (must be 512)              */
) {
	DMA_InitTypeDef DMA_InitStructure;
	WORD rw_workbyte[] = { 0xffff };

	/* shared DMA configuration values */
	DMA_InitStructure.DMA_PeripheralBaseAddr = (DWORD) (&(SPI_SD->DR));
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_BufferSize = btr;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
	DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

	DMA_DeInit(DMA_Channel_SPI_SD_RX);
	DMA_DeInit(DMA_Channel_SPI_SD_TX);

	if (receive) {

		/* DMA1 channel2 configuration SPI1 RX ---------------------------------------------*/
		/* DMA1 channel4 configuration SPI2 RX ---------------------------------------------*/
		DMA_InitStructure.DMA_MemoryBaseAddr = (DWORD) buff;
		DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
		DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
		DMA_Init(DMA_Channel_SPI_SD_RX, &DMA_InitStructure);

		/* DMA1 channel3 configuration SPI1 TX ---------------------------------------------*/
		/* DMA1 channel5 configuration SPI2 TX ---------------------------------------------*/
		DMA_InitStructure.DMA_MemoryBaseAddr = (DWORD) rw_workbyte;
		DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
		DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
		DMA_Init(DMA_Channel_SPI_SD_TX, &DMA_InitStructure);

	} else {

#if _FS_READONLY == 0
		/* DMA1 channel2 configuration SPI1 RX ---------------------------------------------*/
		/* DMA1 channel4 configuration SPI2 RX ---------------------------------------------*/
		DMA_InitStructure.DMA_MemoryBaseAddr = (DWORD) rw_workbyte;
		DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
		DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
		DMA_Init(DMA_Channel_SPI_SD_RX, &DMA_InitStructure);

		/* DMA1 channel3 configuration SPI1 TX ---------------------------------------------*/
		/* DMA1 channel5 configuration SPI2 TX ---------------------------------------------*/
		DMA_InitStructure.DMA_MemoryBaseAddr = (DWORD) buff;
		DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
		DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
		DMA_Init(DMA_Channel_SPI_SD_TX, &DMA_InitStructure);
#endif

	}

	/* Enable DMA RX Channel */
	DMA_Cmd(DMA_Channel_SPI_SD_RX, ENABLE);
	/* Enable DMA TX Channel */
	DMA_Cmd(DMA_Channel_SPI_SD_TX, ENABLE);

	/* Enable SPI TX/RX request */
	SPI_I2S_DMACmd(SPI_SD, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, ENABLE);

	/* Wait until DMA1_Channel 3 Transfer Complete */
	/// not needed: while (DMA_GetFlagStatus(DMA_FLAG_SPI_SD_TC_TX) == RESET) { ; }
	/* Wait until DMA1_Channel 2 Receive Complete */
	while (DMA_GetFlagStatus(DMA_FLAG_SPI_SD_TC_RX) == RESET) {
		;
	}
	// same w/o function-call:
	// while ( ( ( DMA1->ISR ) & DMA_FLAG_SPI_SD_TC_RX ) == RESET ) { ; }

	/* Disable DMA RX Channel */
	DMA_Cmd(DMA_Channel_SPI_SD_RX, DISABLE);
	/* Disable DMA TX Channel */
	DMA_Cmd(DMA_Channel_SPI_SD_TX, DISABLE);

	/* Disable SPI RX/TX request */
	SPI_I2S_DMACmd(SPI_SD, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, DISABLE);
}
#endif /* STM32_SD_USE_DMA */

/*-----------------------------------------------------------------------*/
/* Power Control and interface-initialization (Platform dependent)       */
/*-----------------------------------------------------------------------*/

static void power(void) {
	static uint32_t i;
	SPI_InitTypeDef SPI_InitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	volatile uint8_t dummyread;

	/* Enable GPIO clock for CS */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_CS | RCC_APB2Periph_GPIO_SD,
			ENABLE);
	/* Enable SPI clock, SPI1: APB2, SPI2: APB1 */
	RCC_APB2PeriphClockCmd(RCC_APBPeriph_SPI_SD, ENABLE);

	for (i = 2000; i > 0; i--) {
		;
	} /* wait for ready*/

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_SPI_SD_CD;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIO_SPI_SD, &GPIO_InitStructure);

	/* Configure I/O for Flash Chip select */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_CS;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIO_CS, &GPIO_InitStructure);

	/* Configure SPI pins: SCK and MOSI with default alternate function (not re-mapped) push-pull */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_SPI_SD_SCK | GPIO_Pin_SPI_SD_MOSI;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(GPIO_SPI_SD, &GPIO_InitStructure);
	/* Configure MISO as Input with internal pull-up */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_SPI_SD_MISO;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIO_SPI_SD, &GPIO_InitStructure);

	/* De-select the Card: Chip Select high */
	DESELECT();

	/* SPI configuration */
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; // 72000kHz/256=281kHz < 400kHz
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
	SPI_InitStructure.SPI_CRCPolynomial = 7;

	SPI_Init(SPI_SD, &SPI_InitStructure);
	SPI_CalculateCRC(SPI_SD, DISABLE);
	SPI_Cmd(SPI_SD, ENABLE);

	/* drain SPI */
	while (SPI_I2S_GetFlagStatus(SPI_SD, SPI_I2S_FLAG_TXE) == RESET) {
		;
	}
	dummyread = SPI_I2S_ReceiveData(SPI_SD);

#ifdef STM32_SD_USE_DMA
	/* enable DMA clock */
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
#endif
}

static
void power_off(void) {
	GPIO_InitTypeDef GPIO_InitStructure;

	SELECT();
	wait_ready();/* escape if receive is oxff or time2 is out*/
	release_spi(); /* CS = HIGH*/

	SPI_I2S_DeInit(SPI_SD);
	SPI_Cmd(SPI_SD, DISABLE);
	RCC_APB2PeriphClockCmd(RCC_APBPeriph_SPI_SD, DISABLE);

	/* All SPI-Pins to input with weak internal pull-downs */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_SPI_SD_SCK | GPIO_Pin_SPI_SD_MISO
			| GPIO_Pin_SPI_SD_MOSI | GPIO_Pin_SPI_SD_CD;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
	GPIO_Init(GPIO_SPI_SD, &GPIO_InitStructure);

	Stat = STA_NOINIT;
}

/*-----------------------------------------------------------------------*/
/* Receive a data packet from MMC                                        */
/*-----------------------------------------------------------------------*/

static bool rcvr_datablock(uint8_t *buff, /* Data buffer to store received data */
uint16_t btr /* Byte count (must be multiple of 4) */
) {
	uint8_t token;
	static uint32_t i;

	i = 9000;
	do { /* Wait for data packet in timeout of 100ms */
		token = rcvr_spi(); /* Wait for valid response oxfe*/
		i--;
	} while ((token == 0xFF) && i > 0); /*if escape, token = oxff if timer1 is out, else token=oxfe*/
	if (token != 0xFE)
		return FALSE; /* If not valid data token, return with error */
#ifdef STM32_SD_USE_DMA
	stm32_dma_transfer(TRUE, buff, btr);
#else
	do { /* Receive the data block into buffer */
		rcvr_spi_m(buff++);
		rcvr_spi_m(buff++);
		rcvr_spi_m(buff++);
		rcvr_spi_m(buff++);
	}while (btr -= 4);
#endif /* STM32_SD_USE_DMA */

	rcvr_spi(); /* Discard CRC */
	rcvr_spi();

	return TRUE; /* Return with success */
}

/*-----------------------------------------------------------------------*/
/* Send a data packet to MMC                                             */
/*-----------------------------------------------------------------------*/
/* No use in my thesic */

static bool xmit_datablock(const uint8_t *buff, /* 512 byte data block to be transmitted */
uint8_t token /* Data/Stop token */
) {
	uint8_t resp;
#ifndef STM32_SD_USE_DMA
	uint8_t wc;
#endif
	if (wait_ready() != 0xFF)
		return FALSE;

	xmit_spi(token); /* transmit data token */
	if (token != 0xFD) { /* Is data token */

#ifdef STM32_SD_USE_DMA
		stm32_dma_transfer(FALSE, buff, 512);
#else
		wc = 0;
		do { /* transmit the 512 byte data block to MMC */
			xmit_spi(*buff++);
			xmit_spi(*buff++);
		}while (--wc);
#endif /* STM32_SD_USE_DMA */

		xmit_spi(0xFF); /* CRC (Dummy) */
		xmit_spi(0xFF);
		resp = rcvr_spi(); /* Receive data response */
		if ((resp & 0x1F) != 0x05) /* If not accepted, return with error */
			return FALSE;
	} /* illegal command and in idle state*/
	return TRUE;
}

/*-----------------------------------------------------------------------*/
/* Send a command packet to MMC                                          */
/*-----------------------------------------------------------------------*/

static uint8_t send_cmd(uint8_t cmd, /* Command byte */
uint32_t arg /* Argument  usually is address*/
) {
	uint8_t n, res;

	/* Select the card and wait for ready */
	DESELECT();
	SELECT();
	if (wait_ready() != 0xFF) /* time out*/
		return 0xFF;

	/* Send command packet */
	xmit_spi(cmd); /* Start + Command index */
	xmit_spi((uint8_t)(arg >> 24)); /* Argument[31..24] */
	xmit_spi((uint8_t)(arg >> 16)); /* Argument[23..16] */
	xmit_spi((uint8_t)(arg >> 8)); /* Argument[15..8] */
	xmit_spi((uint8_t)arg); /* Argument[7..0] */
	n = 0x01; /* Dummy CRC + Stop */
	if (cmd == CMD0)
		n = 0x95; /* Valid CRC for CMD0(0) */
	if (cmd == CMD8)
		n = 0x87; /* Valid CRC for CMD8(0x1AA) */
	xmit_spi(n);

	/* Receive command response */
	if (cmd == CMD12)
		rcvr_spi(); /* Skip a stuff byte when stop reading */

	n = 20; /* Wait for a valid response in timeout of 10 attempts */
	do
		res = rcvr_spi();
	while ((res & 0x80) && --n); /* wait for valid respose*/

	return res; /* Return with the response value */
}

/*--------------------------------------------------------------------------

 Public Functions

 ---------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------*/
/* Initialize Disk Drive                                                 */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize(uint8_t drv /* Physical drive number (0) */
) {
	uint8_t n;
	uint32_t i;
	if (drv) {
		Stat = STA_NOINIT;
		return STA_NOINIT; /* Supports only single drive */
	}
	power();
	if (CardDetected() == TRUE) {
		Stat = STA_NODISK;
		return STA_NODISK;
	}
	interface_speed(INTERFACE_SLOW);
	DESELECT();
	for (n = 12; n; n--)
		rcvr_spi(); /* 80 dummy clocks */

	if (send_cmd(CMD0, 0) == 1) { /* Enter Idle state */
		i = 8200; /* Initialization timeout of 1000 milliseconds */
		do {
			i--;
		} while (send_cmd(CMD1, 0) != 0 && i > 0); /* Wait for leaving idle state */
		release_spi();

		if (i == 0) {
			power_off();
			Stat = STA_NOINIT;
			return STA_NOINIT;
		} else {
			interface_speed(INTERFACE_FAST);
			Stat = 0;
			return 0;
		}
	}
	power_off();
	Stat = STA_NOINIT;
	return STA_NOINIT;
}

/*-----------------------------------------------------------------------*/
/* Get Disk Status                                                       */
/*-----------------------------------------------------------------------*/

DSTATUS disk_status(BYTE drv /* Physical drive number (0) */
) {
	if (drv)
		return STA_NOINIT; /* Supports only single drive */
	return Stat;
}

/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

DRESULT disk_read(uint8_t drv, /* Physical drive number (0) */
uint8_t *buff, /* Pointer to the data buffer to store read data */
uint32_t sector, /* Start sector number (LBA) */
uint8_t count /* Sector count (1..255) */
) {
	if (drv || !count)
		return RES_PARERR;
	if (Stat == STA_NOINIT)
		return RES_NOTRDY;
	sector *= 512; /* get address of sector */

	if (count == 1) { /* Single block read */
		if (send_cmd(CMD17, sector) == 0) { /* READ_SINGLE_BLOCK */
			if (rcvr_datablock(buff, 512)) {
				count = 0;
			}
		}
	} else { /* Multiple block read */
		if (send_cmd(CMD18, sector) == 0) { /* READ_MULTIPLE_BLOCK */
			do {
				if (!rcvr_datablock(buff, 512)) {
					break;
				}
				buff += 512;
			} while (--count);
			send_cmd(CMD12, 0); /* STOP_TRANSMISSION */
		}
	}
	release_spi();
	return count ? RES_ERROR : RES_OK;
}

/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

DRESULT disk_write(uint8_t drv, /* Physical drive number (0) */
const uint8_t *buff, /* Pointer to the data to be written */
uint32_t sector, /* Start sector number (LBA) */
uint8_t count /* Sector count (1..255) */
) {

	if (drv || !count)
		return RES_PARERR;
	if (Stat == STA_NOINIT)
		return RES_NOTRDY;
	sector *= 512; /*get address of sector*/

	if (count == 1) { /* Single block write */
		if ((send_cmd(CMD24, sector) == 0) /* WRITE_BLOCK */
		&& xmit_datablock(buff, 0xFE))
			count = 0;
	} else { /* Multiple block write */
		if (send_cmd(CMD25, sector) == 0) { /* WRITE_MULTIPLE_BLOCK */
			do {
				if (!xmit_datablock(buff, 0xFC))
					break;
				buff += 512;
			} while (--count);
			if (!xmit_datablock(0, 0xFD)) /* STOP_TRAN token */
				count = 1;
		}
	}
	release_spi();
	return count ? RES_ERROR : RES_OK;
}

